package com.hex.ds.hdtp.core.app.license.activation.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.hex.ds.hdtp.core.app.license.activation.converter.IFuncActivationConverter;
import com.hex.ds.hdtp.core.app.license.activation.dto.RsaUtil;
import com.hex.ds.hdtp.core.app.license.activation.dto.request.FuncActivationLogRequestDto;
import com.hex.ds.hdtp.core.app.license.activation.dto.request.FuncActivationRequestDto;
import com.hex.ds.hdtp.core.app.license.activation.dto.response.ScriptConvertSurplusResponseDto;
import com.hex.ds.hdtp.core.app.license.activation.service.IFuncActivationLogService;
import com.hex.ds.hdtp.core.app.license.activation.service.IFuncActivationService;
import com.hex.ds.hdtp.core.app.license.truelicense.dto.LicenseCheckModel;
import com.hex.ds.hdtp.core.app.license.truelicense.dto.LicenseVerifyParam;
import com.hex.ds.hdtp.core.app.license.truelicense.service.LicenseVerify;
import com.hex.ds.hdtp.core.app.script.transfer.dto.response.ScriptConvertSourceListResponseDto;
import com.hex.ds.hdtp.core.inf.license.activation.po.FuncActivationPo;
import com.hex.ds.hdtp.core.inf.license.activation.repository.IFuncActivationLogRepository;
import com.hex.ds.hdtp.core.inf.license.activation.repository.IFuncActivationRepository;
import de.schlichtherle.license.LicenseContent;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import org.springframework.core.io.Resource;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor(onConstructor_ = {@Lazy, @Autowired})
public class FuncActivationService implements IFuncActivationService {

    private final IFuncActivationConverter converter;

    private final IFuncActivationRepository funcActivationRepository;

    private final IFuncActivationLogRepository funcActivationLogRepository;

    private final IFuncActivationLogService funcActivationLogService;

    private final RsaUtil rsaUtil;

    // 证书主题
    @Value("${trueLicense.subject}")
    private String subject;

    // 公钥别称
    @Value("${trueLicense.publicAlias}")
    private String publicAlias;

    // 访问公钥库的密码
    @Value("${trueLicense.storePass}")
    private String storePass;

    // 证书备份路径
    @Value("${trueLicense.licensePath}")
    private String licensePath;

    // 公钥存储路径
    @Value("${trueLicense.publicKeysStorePath}")
    private String publicKeysStorePath;

    // 公钥相对路径
    @Value("classpath:static/license/publicCerts.keystore")
    private Resource publicKeysStoreResource;

    // 项目地址
    @Value("${hdtp.home}")
    private String hdtpHome;

//    private final String ACTIVATION_FILE_PATH = "~/.hd8tp-config";
//    private final String ACTIVATION_LFILE_PATH = "~/.hd8tp-lconfig";

    private String ACTIVATION_FILE_PATH;
    private String ACTIVATION_LFILE_PATH;
    @PostConstruct
    private void initPaths() {
        ACTIVATION_FILE_PATH = hdtpHome + "\\hd8tp-config";
        ACTIVATION_LFILE_PATH = hdtpHome + "\\hd8tp-lconfig";
    }

    /**
     * @Method uploadLicense
     * @Param license
     * @Return void
     * @Description 上传激活license
     * @Author gj.xu
     * @Date 2024/3/26 19:11
     * @Version V2.0
     */
    public void uploadLicense(List<MultipartFile> multipartFileList) throws Exception {
        // 验证license
        LicenseCheckModel expectedCheckModel = verifyLicense(CollUtil.getFirst(multipartFileList));
        if (Boolean.TRUE.equals(expectedCheckModel.getIsReset())) {
            resetLicense();
            throw new RuntimeException("重置license成功");
        }
        verifyLicenseUnique(expectedCheckModel.getRsaLicense());
        checkActivation();
        // 提取license信息
        FuncActivationLogRequestDto funcActivationLogRequestDto = converter.toFuncActivationLogRequestDto(
                expectedCheckModel.getRsaLicense(),
                DateUtil.formatDateTime(new Date()));
        FuncActivationRequestDto funcActivationRequestDto = converter.toFuncActivationRequestDto(
                rsaUtil.getEncrypt(StrUtil.toString(expectedCheckModel.getScriptConvertFrequency() + expectedCheckModel.getScriptConvertFileSize())),
                String.join(", ", expectedCheckModel.getMacAddress()),
                "ENABLE",
                expectedCheckModel.getScriptConvertFrequency(),
                expectedCheckModel.getScriptConvertFileSize());

        // 新增license记录
        funcActivationLogService.add(funcActivationLogRequestDto);

        // 判断激活表是否只有1条数据，是则修改剩余量，否则清空新增
        String activationStatus = funcActivationRequestDto.getActivationStatus();
        if (funcActivationRepository.selectAllCount() == 1) {
            // 计算新license激活后剩余量，并修改激活表
            activationStatus = calcSurplus(funcActivationRequestDto);
        } else {
            funcActivationRepository.deleteAll();
            add(funcActivationRequestDto);
        }
        // 修改隐藏文件内容
        modifyFileInfo(getKeyWithTime(activationStatus), ACTIVATION_FILE_PATH);
    }

    /**
     * @Method calcSurplus
     * @Param funcActivationRequestDto
     * @Return java.lang.String
     * @Description 计算新license激活后剩余量，并修改激活表
     * @Author gj.xu
     * @Date 2024/3/26 19:11
     * @Version V2.0
     */
    private String calcSurplus(FuncActivationRequestDto funcActivationRequestDto) {
        FuncActivationPo funcActivationPo = funcActivationRepository.selectOne();
        if ("ENABLE".equals(funcActivationPo.getScriptConvertStatus())) {
            // 激活表为 激活状态 时累加
            funcActivationPo.setScriptConvertFrequency(funcActivationPo.getScriptConvertFrequency() + funcActivationRequestDto.getScriptConvertFrequency());
            funcActivationPo.setScriptConvertFileSize(funcActivationPo.getScriptConvertFileSize() + funcActivationRequestDto.getScriptConvertFileSize());
            String activationStatus = getSurplusCode(funcActivationPo);
            funcActivationPo.setActivationStatus(activationStatus);
            funcActivationRepository.update(funcActivationPo);
            return activationStatus;
        } else {
            // 激活表数据为 未激活状态 时使用新license中剩余量
            funcActivationRepository.deleteAll();
            add(funcActivationRequestDto);
            return funcActivationRequestDto.getActivationStatus();
        }
    }

    /**
     * @Method modifyFileInfo
     * @Param newData
     * @Return void
     * @Description 修改隐藏文件
     * @Author gj.xu
     * @Date 2024/3/26 19:11
     * @Version V2.0
     */
    public void modifyFileInfo(String newData, String path) {
        File file = new File(path);
        boolean appendNewLine = true;
        if (!file.exists()) {
            try {
                file.createNewFile();
                appendNewLine = false;
                log.info("隐藏文件创建成功");
            } catch (IOException e) {
                log.error("隐藏文件创建失败", e);
                return;
            }
        }
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(file, path.equals(ACTIVATION_LFILE_PATH)))) {
            if (path.equals(ACTIVATION_LFILE_PATH) && appendNewLine) {
                // 如果是证书隐藏文件，则先添加换行符，再拼接
                writer.newLine();
            }
            writer.write(newData);
            log.info("隐藏数据写入成功");
        } catch (IOException e) {
            log.error("隐藏数据写入失败", e);
        }
    }



    /**
     * @Method readFileInfo
     * @Param
     * @Return java.lang.String
     * @Description 读取隐藏文件的解密内容
     * @Author gj.xu
     * @Date 2024/3/26 19:10
     * @Version V2.0
     */
    public String readFileInfo(String path) {
        File file = new File(path);
        StringBuilder content = new StringBuilder();
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
            BufferedReader reader = new BufferedReader(new FileReader(path));
            String line;
            while ((line = reader.readLine()) != null) {
                content.append(line);
                content.append("\n");
            }

        } catch (IOException e) {
            System.out.println("An error occurred while reading the file: " + e.getMessage());
        }
        return content.toString().trim();
    }

    /**
     * @Method checkActivation
     * @Param
     * @Return java.lang.Boolean
     * @Description 三方验证是否存在被破解情况
     * @Author gj.xu
     * @Date 2024/3/26 19:09
     * @Version V2.0
     */
    public Boolean checkActivation() {
        FuncActivationPo funcActivationPo = funcActivationRepository.selectOne();
        File file = new File(ACTIVATION_FILE_PATH);
        // 1. 检验是否存在激活记录
        if (funcActivationPo == null) {
            if (!file.exists()) {
                return Boolean.TRUE;
            } else {
                log.warn("发现存在破解行为，请勿擅自删除数据库记录");
                return Boolean.FALSE;
            }
        }
        if (!"ENABLE".equals(funcActivationPo.getScriptConvertStatus())
                || StrUtil.isBlank(funcActivationPo.getServerMac())
                || funcActivationPo.getScriptConvertFrequency() == null
                || funcActivationPo.getScriptConvertFileSize() == null) {
            funcActivationPo.setScriptConvertStatus("DISABLE");
            funcActivationRepository.update(funcActivationPo);
            return Boolean.FALSE;
        }
        // 2. 检验是否有人工修改隐藏文件
        if (!checkFileStatus(ACTIVATION_FILE_PATH)) {
            return Boolean.FALSE;
        }
        // 3. 检验数据库字段是否修改过
        boolean checkActivationStatus = rsaUtil.checkTwoKey(funcActivationPo.getActivationStatus(), getSurplusCode(funcActivationPo))
                && rsaUtil.getDecrypt(funcActivationPo.getActivationStatus()).equals(getFileDecryptValue(ACTIVATION_FILE_PATH));
        if (checkActivationStatus) {
            return Boolean.TRUE;
        } else {
            funcActivationPo.setScriptConvertStatus("DISABLE");
            funcActivationRepository.update(funcActivationPo);
            log.warn("发现存在破解行为");
            return Boolean.FALSE;
        }
    }


    /**
     * @Method checkActivation
     * @Param
     * @Return java.lang.Boolean
     * @Description 三方验证License是否重复认证
     * @Author Yin.Yang
     * @Date 2024/3/29 14:29
     * @Version V1.0
     */
    public Boolean checkLicenseActivation(String newRsaLicense) {
        List<String> activationCodes = funcActivationLogRepository.selectActivationCodes();
        HashSet<String> decryptCodes = activationCodes.stream().map(rsaUtil::getDecrypt).collect(Collectors.toCollection(HashSet::new));
        File file = new File(ACTIVATION_LFILE_PATH);
        // 1. 检验是否存在激活记录
        if (CollUtil.isEmpty(activationCodes)) {
            if (!file.exists()) {
                return Boolean.TRUE;
            } else {
                log.warn("发现存在破解行为，请勿擅自删除数据库记录");
                return Boolean.FALSE;
            }
        }
        // 2. 检验是否有人工修改隐藏文件
        if (!checkFileStatus(ACTIVATION_LFILE_PATH)) {
            return Boolean.FALSE;
        }
        // 3. 检验数据库加密字段是否修改过
        List<String> fileKeys = FileUtil.readLines(ACTIVATION_LFILE_PATH, StandardCharsets.UTF_8);
        HashSet<String> fileDecryptSet = fileKeys.stream()
                .map(rsaUtil::getDecrypt)
                .map(value -> value.substring(0, value.length() - 19))
                .collect(Collectors.toCollection(HashSet::new));
        if (!fileDecryptSet.equals(decryptCodes)) {
            log.warn("发现存在破解行为，请勿擅自修改加密文件");
            return Boolean.FALSE;
        }
        // 4. 检验文件是否重复上传
        if (decryptCodes.contains(rsaUtil.getDecrypt(newRsaLicense))) {
            log.warn("请勿重复上传License文件");
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

    /**
     * @Method getScriptConvertSurplus
     * @Param
     * @Return void
     * @Description 获取脚本转换剩余量
     * @Author gj.xu
     * @Date 2024/3/26 19:49
     * @Version V2.0
     */
    public ScriptConvertSurplusResponseDto getScriptConvertSurplus() {
        checkActivation();
        return converter.toScriptConvertSurplusResponseDto(funcActivationRepository.selectOne());
    }

    /**
     * @Method deductScriptConvertSurplus
     * @Param scriptConvertList
     * @Return com.hex.ds.hdtp.core.app.license.activation.dto.response.ScriptConvertSurplusResponseDto
     * @Description 脚本转换后扣减剩余量
     * @Author Yin.Yang
     * @Date 2024/4/1 19:00
     * @Version V1.0
     */
    public ScriptConvertSurplusResponseDto deductScriptConvertSurplus(ScriptConvertSourceListResponseDto scriptConvertList) {
        FuncActivationPo funcActivationPo = funcActivationRepository.selectOne();
        if (funcActivationPo == null) {
            throw new RuntimeException("未激活license");
        }
        if (funcActivationPo.getScriptConvertFrequency() <= 0 || funcActivationPo.getScriptConvertFileSize() <= 0) {
            throw new RuntimeException("脚本转换剩余量不足");
        }
        funcActivationPo.setScriptConvertFrequency(funcActivationPo.getScriptConvertFrequency() - 1);
        Integer sourceFileSize = Integer.parseInt(scriptConvertList.getSourceFileSize());
        funcActivationPo.setScriptConvertFileSize(funcActivationPo.getScriptConvertFileSize() - sourceFileSize);
        String activationStatus = getSurplusCode(funcActivationPo);
        funcActivationPo.setActivationStatus(activationStatus);
        modifyFileInfo(getKeyWithTime(activationStatus), ACTIVATION_FILE_PATH);
        funcActivationRepository.update(funcActivationPo);
        return converter.toScriptConvertSurplusResponseDto(funcActivationPo);
    }

    /**
     * @Method checkScriptConvertStatus
     * @Param
     * @Return java.lang.Boolean
     * @Description 检查脚本转换状态
     * @Author Yin.Yang
     * @Date 2024/4/3 16:38
     * @Version V1.0
     */
    public Boolean checkScriptConvertStatus() {
        checkActivation();
        FuncActivationPo funcActivationPo = funcActivationRepository.selectOne();
        if (funcActivationPo == null) {
            return Boolean.FALSE;
        }
        return "ENABLE".equals(funcActivationPo.getScriptConvertStatus());
    }

    /**
     * @Method add
     * @Param funcActivationRequestDto
     * @Return void
     * @Description 新增激活表
     * @Author gj.xu
     * @Date 2024/3/26 19:11
     * @Version V2.0
     */
    public void add(FuncActivationRequestDto funcActivationRequestDto) {
        if (funcActivationRepository.add(converter.toFuncActivationPo(funcActivationRequestDto)) != 1) {
            throw new RuntimeException("新增license激活表失败");
        }
    }

    /**
     * @Method verifyLicense
     * @Param licenseFile
     * @Return com.hex.ds.hdtp.core.app.license.truelicense.dto.LicenseCheckModel
     * @Description 验证证书
     * @Author Yin.Yang
     * @Date 2024/4/9 18:38
     * @Version V1.0
     */
    public LicenseCheckModel verifyLicense(MultipartFile licenseFile) throws Exception {
        if (licenseFile.isEmpty()) {
            throw new RuntimeException("文件列表为空");
        }

        // 计算RSA加密后的证书
        String hashLicense = getSha256Hex(IoUtil.readBytes(licenseFile.getInputStream()));
        String newRsaLicense = rsaUtil.getEncrypt(hashLicense);

        // 备份license证书文件
        FileUtil.mkdir(licensePath);
        String absoluteLicensePath = licensePath + File.separator + licenseFile.getOriginalFilename();
        File serverFile = new File(absoluteLicensePath);
        licenseFile.transferTo(serverFile);

        // 拷贝公钥库文件
        FileUtil.copy(publicKeysStoreResource.getFile(), new File(publicKeysStorePath), true);
        // 验证license
        log.info("++++++++ 开始验证证书 ++++++++");
        LicenseVerifyParam param = new LicenseVerifyParam();
        param.setSubject(subject).setPublicAlias(publicAlias).setStorePass(storePass)
                .setLicensePath(absoluteLicensePath).setPublicKeysStorePath(publicKeysStorePath);
        LicenseVerify licenseVerify = new LicenseVerify();
        LicenseContent verifyContent = licenseVerify.install(param);
        LicenseCheckModel expectedCheckModel = (LicenseCheckModel) verifyContent.getExtra();
        expectedCheckModel.setRsaLicense(newRsaLicense);
        log.info("++++++++ 证书验证成功 ++++++++");
        return expectedCheckModel;
    }

    /**
     * @Method verifyLicense
     * @Param licenseFile
     * @Return com.hex.ds.hdtp.core.app.license.truelicense.dto.LicenseCheckModel
     * @Description 验证证书是否重复上传
     * @Author Yin.Yang
     * @Date 2024/3/28 17:16
     * @Version V1.0
     */
    private Boolean verifyLicenseUnique(String newRsaLicense) {
        // 验证是否重复上传
        if (!checkLicenseActivation(newRsaLicense)) {
            throw new RuntimeException("该证书文件已上传并验证通过，不能重复上传");
        }
        // 修改License隐藏文件内容
        modifyFileInfo(getKeyWithTime(newRsaLicense), ACTIVATION_LFILE_PATH);
        return true;
    }

    /**
     * @Method resetLicense
     * @Param
     * @Return void
     * @Description 重置license校验
     * @Author Yin.Yang
     * @Date 2024/4/9 18:42
     * @Version V1.0
     */
    private void resetLicense() {
        boolean delFILE = FileUtil.del(new File(ACTIVATION_FILE_PATH));
        boolean delLFILE = FileUtil.del(new File(ACTIVATION_LFILE_PATH));
        if (!delFILE || !delLFILE) {
            throw new RuntimeException("重置license失败，请重启平台");
        }
        funcActivationRepository.deleteAll();
        funcActivationLogRepository.deleteAll();
    }


    /**
     * @Method checkFileStatus
     * @Param filePath
     * @Return java.lang.Boolean
     * @Description 检验是否有人工修改隐藏文件
     * @Author Yin.Yang
     * @Date 2024/4/7 11:57
     * @Version V1.0
     */
    private Boolean checkFileStatus(String filePath) {
        Date fileModifiedTime = DateUtil.parse(getFileModifiedTime(filePath));
        Date decryptTime = DateUtil.parse(getFileDecryptTime(filePath));
        long timeDifference = DateUtil.between(fileModifiedTime, decryptTime, DateUnit.MINUTE);
        log.info("文件修改时间：{}，文件解密时间：{}，时间差（分钟）：{}", fileModifiedTime, decryptTime, timeDifference);
        if (!(timeDifference < 5)) {
            log.warn("发现存在破解行为，请勿擅自修改加密文件");
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }

    /**
     * @Method getSha256Hex
     * @Param source
     * @Return java.lang.String 16进制字符串
     * @Description SHA-256加密
     * @Author Yin.Yang
     * @Date 2024/3/29 13:58
     * @Version V1.0
     */
    private String getSha256Hex(Object source) {
        if (source instanceof byte[]) {
            return DigestUtil.sha256Hex((byte[]) source);
        } else if (source instanceof File) {
            return DigestUtil.sha256Hex((File) source);
        } else {
            return DigestUtil.sha256Hex(source.toString() + "hd8tp");
        }
    }

    /**
     * @Method getSurplusCode
     * @Param source
     * @Return java.lang.String
     * @Description 获取转换剩余量的密文
     * @Author Yin.Yang
     * @Date 2024/4/2 19:32
     * @Version V1.0
     */
    private String getSurplusCode(FuncActivationPo funcActivationPo) {
        String surplusValue = StrUtil.toString(funcActivationPo.getScriptConvertFrequency() + funcActivationPo.getScriptConvertFileSize());
        return rsaUtil.getEncrypt(surplusValue);
    }

    /**
     * @Method getFileModifiedTime
     * @Param path
     * @Return java.lang.String
     * @Description 获取文件修改时间
     * @Author Yin.Yang
     * @Date 2024/4/2 18:16
     * @Version V1.0
     */
    private static String getFileModifiedTime(String path) {
        Date modifiedTime = FileUtil.lastModifiedTime(path);
        return DateUtil.formatDateTime(modifiedTime);
    }

    /**
     * @Method getDecryptTime
     * @Param path
     * @Return java.lang.String
     * @Description 获取文件解密后的时间
     * @Author Yin.Yang
     * @Date 2024/4/2 18:37
     * @Version V1.0
     */
    private String getFileDecryptTime(String path) {
        String fileContent = readFileInfo(path);
        if (path.equals(ACTIVATION_LFILE_PATH) && StrUtil.contains(fileContent, '\n')) {
            fileContent = StrUtil.subAfter(fileContent, '\n', true);
        }
        String decryptValue = rsaUtil.getDecrypt(fileContent);
        return StrUtil.subSufByLength(decryptValue, 19);
    }

    /**
     * @Method getFileDecryptValue
     * @Param path
     * @Return java.lang.String
     * @Description 获取文件解密后的明文
     * @Author Yin.Yang
     * @Date 2024/4/3 11:54
     * @Version V1.0
     */
    private String getFileDecryptValue(String path) {
        String fileContent = readFileInfo(path);
        if (path.equals(ACTIVATION_LFILE_PATH) && StrUtil.contains(fileContent, '\n')) {
            fileContent = StrUtil.subAfter(fileContent, '\n', true);
        }
        String decryptValue = rsaUtil.getDecrypt(fileContent);
        return decryptValue.substring(0, decryptValue.length() - 19);
    }

    /**
     * @Method getKeyWithTime
     * @Param oldKey
     * @Return java.lang.String
     * @Description 重新计算带时间的RSA密文
     * @Author Yin.Yang
     * @Date 2024/4/3 14:18
     * @Version V1.0
     */
    private String getKeyWithTime(String oldKey) {
        String time = DateUtil.formatDateTime(new Date());
        String value = rsaUtil.getDecrypt(oldKey);
        return rsaUtil.getEncrypt(value + time);
    }

}
